Skip to content

现在要用 ESP32-S3 通过 DMA 把一张 240×320 的图片 显示到 ST7789 屏幕上。整个思路其实和之前用 DMA 填充纯色时一模一样,只是数据不再是固定的颜色,而是从图片数组里读取。

🖼️ 第一步:准备图片数据

屏幕的颜色格式是 RGB565(每个像素2个字节,高字节在前)。
你需要把图片转换成这种格式的 C 数组,然后放进你的工程里。

方法一:用 Python 脚本批量转换(推荐)

将图片(PNG/JPG)放在 image.png,运行下面脚本生成 image.h

python
from PIL import Image

def rgb888_to_rgb565(r, g, b):
    return ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | ((b & 0xF8) >> 3)

img = Image.open("image.png").convert("RGB")
# 如果你的图片不是 240x320,先缩放到这个尺寸(居中裁剪或拉伸,这里直接用 resize)
img = img.resize((240, 320), Image.Resampling.LANCZOS)

with open("image.h", "w") as f:
    f.write("#ifndef IMAGE_H\n#define IMAGE_H\n\n#include <stdint.h>\n\n")
    f.write("const uint8_t image_data[] = {\n")
    pixels = img.load()
    count = 0
    for y in range(320):
        for x in range(240):
            r, g, b = pixels[x, y]
            rgb565 = rgb888_to_rgb565(r, g, b)
            # 高字节在前(ST7789 默认)
            f.write(f"0x{(rgb565 >> 8) & 0xFF:02X}, ")
            f.write(f"0x{rgb565 & 0xFF:02X}, ")
            count += 2
            if count % 32 == 0:  # 每16个像素换行
                f.write("\n")
    f.write("};\n\n#endif\n")

运行后得到 image.h,把它放到你的 main 目录里,然后在 main.c 中包含它。

方法二:用在线工具(如 image2cpp

image2cpp 可以选择输出为 RGB565 的 C 数组,注意勾选 “MSB first”(高字节在前)。

📦 第二步:在代码中引用图片数据

c
#include "image.h"

extern const uint8_t image_data[];  // 在 image.h 中定义

🚀 第三步:编写 DMA 显示图片的函数

你可以复用之前纯色填充的框架,只是把“填充固定颜色”改成“从数组中读取颜色”。
因为 240×320×2 = 153600 字节,单次 SPI 传输有大小限制(一般最大 4096 字节),所以需要 分块发送

c
/**
 * @brief 显示一张 240x320 的图片(RGB565)
 * @param spi SPI设备句柄
 * @param image_data 图片数据指针(必须按行顺序排列,每个像素高字节在前)
 */
void lcd_display_image_dma(spi_device_handle_t spi, const uint8_t *image_data)
{
    // 1. 设置全屏窗口
    st7789_set_window(0, 0, 239, 319, spi);

    // 2. 发送写内存命令
    st7789_write_cmd(0x2C, spi);

    // 3.DC 引脚拉高(数据模式)
    gpio_set_level(lcd_spi_dc, 1);

    // 4. 计算总数据量
    const uint32_t total_bytes = 240 * 320 * 2;  // 153600

    // 5. 分块发送,每次最多 4096 字节(可根据硬件调整)
    const uint32_t chunk_size = 4096;
    uint32_t sent = 0;

    while (sent < total_bytes) {
        uint32_t remaining = total_bytes - sent;
        uint32_t this_time = (remaining > chunk_size) ? chunk_size : remaining;

        spi_transaction_t trans = {
            .tx_buffer = image_data + sent,   // 从当前位置取数据
            .length = this_time * 8,          // 单位:位
        };
        spi_device_transmit(spi, &trans);     // 使用 DMA

        sent += this_time;
    }
}

关键点

  • spi_device_transmit 会自动使用 DMA(你在 spi_bus_initialize 时开启了 SPI_DMA_CH_AUTO)。
  • 分块发送不会影响显示,因为屏幕会连续接收数据,只要顺序正确,它会自动填充到窗口内。

🧪 第四步:在 app_main 中调用

c
void app_main(void)
{
    // ... 初始化 GPIO、SPI、ST7789(和之前一样) ...

    // 显示图片(假设图片数据在 image_data 中)
    lcd_display_image_dma(spi_device_handle, image_data);

    // 保持显示
    while (1) {
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

💡 关于“分块大小”的说明

  • chunk_size 可以选择 4096、8192 或 16384,但不能超过 max_transfer_sz(你在 spi_bus_config_t 里设置的)。
    如果你设置了 .max_transfer_sz = 240 * 320 * 2 + 8,则理论上可以一次性发送 153608 字节,但 单次 SPI 传输的实际大小受 DMA 描述符数量和内存限制,通常设为 4096 或 8192 比较稳妥。
    你可以自行测试并增大,以提高效率。

🔁 如果图片数据存在外部 Flash(如 SPIFFS)怎么办?

你可以从文件系统读取到缓冲区,再发送。但为了简化,上面直接用了内置数组,适合初学者。

🎯 总结

  • 图片转成 RGB565 数组(高字节在前)。
  • st7789_set_window 设置全屏。
  • spi_device_transmit 分块发送数组数据,自动使用 DMA。
  • 整个过程和你之前纯色填充的逻辑一致,只是数据源从生成的颜色变成了图片数组。

现在,把图片转换脚本跑一下,生成 image.h,然后编译烧录,你就能在屏幕上看到自己的图片了!如果颜色不对,检查一下字节序(高字节是否在前)和 RGB 顺序。 😊

觉醒,然后燎原。 © 2026 门主引擎